#pragma once

#include "Log.h"
#include "Error.h"
#include <unistd.h>
#include <sys/epoll.h>
#include <fcntl.h>


class Epoll {
public:
    constexpr static int DefaultEpollFd = -1;

private:
    int _epoll_fd; // epoll模型的句柄

public:
    Epoll()
        : _epoll_fd(DefaultEpollFd)
    {}

    ~Epoll() {
        if (_epoll_fd != DefaultEpollFd) ::close(_epoll_fd);
    }

public:
    // 创建epoll模型
    void create() {
        int size = 8; // epoll_create的参数，现已被废除，任意一个大于0的数即可
        _epoll_fd = epoll_create(size);
        if (_epoll_fd < 0) {
            logMessage(FATAL, "create epoll fail!");
            exit(EPOLL_CREATE_ERR);
        }
        logMessage(NORMAL, "epoll %d create success!", _epoll_fd);
    }

    /**
     * 向epoll中添加需要关心的fd和该fd上的哪种事件
     * 
     * @param sockfd 需要epoll托管的fd
     * @param events 需要关心sockfd上的哪种事件
     * @return 返回true表示添加成功，false表示添加失败
    */
    bool add(const int& sockfd, const uint32_t& events) {
        if (events & EPOLLET) setNonBlock(sockfd); // 如果sockfd是ET模式就给他设置为非阻塞状态
        struct epoll_event ev;
        ev.events = events;
        ev.data.fd = sockfd;

        int ret = epoll_ctl(_epoll_fd, EPOLL_CTL_ADD, sockfd, &ev);
        return ret == 0;
    }

    /**
     * 更改epoll中sockfd上需要关系的事件
     * 
     * @param sockfd 需要进行更改的fd
     * @param events 更改后需要关心sockfd上的哪种事件
     * @return 返回true表示更改成功，false表示更改失败
    */
    bool modify(const int& sockfd, const uint32_t& events) {
        struct epoll_event ev;
        ev.events = events;
        ev.data.fd = sockfd;

        int ret = epoll_ctl(_epoll_fd, EPOLL_CTL_MOD, sockfd, &ev);
        return ret == 0;
    }

    /**
     * 在epoll移除sockfd, 即让epoll不再托管sockfd和不再关心其上的事件
     * 
     * @param sockfd 不再需要托管的sockfd
     * @return 返回true表示删除成功，false表示删除失败
    */
    bool remove(const int& sockfd) {
        int ret = epoll_ctl(_epoll_fd, EPOLL_CTL_DEL, sockfd, nullptr);
        return ret == 0;
    }

    /**
     * 让epoll开始等待事件发生
     * 
     * @param events 存放就绪事件的数组
     * @param len 存放就绪事件的数组的长度
     * @param timeout epoll每次等待事件发生的时间。timeout == -1: 阻塞式等待; timeout == 0: 非阻塞式等待; timeout > 0: epoll每次等待timeout(ms)
     * @return 返回值 > 0: 就绪事件的个数; 返回值 == 0: 等待超时，没有等待到事件; 返回值 == -1: 等待发生问题, errno被设置
    */
    int wait(struct epoll_event* events, const int& len, const int& timeout) {
        int n = epoll_wait(_epoll_fd, events, len, timeout);
        return n;
    }

    // 关闭epoll
    void close() {
        if (_epoll_fd != DefaultEpollFd) ::close(_epoll_fd);
    }

private:
    /**
     * 将目标文件描述符设为非阻塞状态
     ** ET(边缘触发)模式要求必须将epoll托管的fd设置为非阻塞状态
     * 
     * @param fd 需要设置为非阻塞状态的文件描述符
     * @return true为设置成功，false为设置失败
    */
    bool setNonBlock(const int& fd) {
        int flag = fcntl(fd, F_GETFL); // 获得fd当前的状态如读写("w", "r")状态
        if (flag < 0) return false;
        fcntl(fd, F_SETFL, flag | O_NONBLOCK);
        return true;
    }

}; //@end class Epoll